<?php
/**
 * Project: AIGC
 * Author: moufer <moufer@163.com>
 * Date: 2024/7/17 23:27
 */

namespace module\user\logic;

use app\library\Email;
use app\library\Sms;
use app\logic\CaptchaLogic;
use module\user\enum\CaptchaEmsEventEnum;
use module\user\front\input\LoginInput;
use module\user\front\input\RegisterInput;
use module\user\model\User;
use mof\annotation\Inject;
use mof\exception\LogicException;
use mof\interface\AuthInterface;
use mof\interface\UserInterface;
use mof\Logic;
use mof\Model;
use mof\Module;
use PHPMailer\PHPMailer\Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Db;
use think\facade\Log;
use think\helper\Str;

class PassportLogic extends Logic
{
    #[Inject(User::class)]
    protected $model;

    /**
     * @param LoginInput $login
     * @param AuthInterface $auth
     * @return UserInterface
     * @throws DbException
     */
    public function login(LoginInput $login, AuthInterface $auth): UserInterface
    {
        if ($login->type === LoginInput::TYPE_SMS) {
            //手机号+验证码登录
            $user = $this->model->where('mobile', $login->mobile)->find();
            if (!$user) {
                //手机号登录，自动注册
                $params = [];
                $login->invite && $params['invite'] = $login->invite;
                $user = $this->registerByMobile($login->mobile, $params);
            }
        } else {
            //用户名+密码登录
            $user = $this->model->where('username', $login->username)->find();
            if (!$user) throw new LogicException('用户不存在');
            if (!password_verify($login->password, $user->password)) {
                throw new LogicException('密码错误');
            }
        }

        if ($user->status !== 1) {
            throw new LogicException('当前账号已被禁用');
        }

        $lastLoginAt = $user->login_at;
        if (!$auth->login($user)) {
            throw new LogicException('登录失败');
        }

        $user->login_at = date('Y-m-d H:i:s');
        $user->save();

        //触发数据累加时间(仅记录上次登录是昨天时)
        if (date('Ymd') !== date('Ymd', strtotime($lastLoginAt))) {
            event('system.total', ['name' => 'user.login']);
        }

        //登录事件
        event('user.login', ['login' => $login, 'user' => $user]);

        return $user;
    }

    /**
     * 通过用户名注册
     * @param object<RegisterInput> $input
     * @return User
     * @throws DbException
     */
    public function register(object $input, AuthInterface $auth = null): Model
    {
        //检测username/mobile/email是否已存在
        if ($this->model->where('username', $input->username)->count()) {
            throw new LogicException('用户名已被注册');
        }
        if ($input->mobile && $this->model->where('mobile', $input->mobile)->count()) {
            throw new LogicException('手机号已被注册');
        }
        if ($input->email && $this->model->where('email', $input->mobile)->count()) {
            throw new LogicException('邮箱已被注册');
        }
        //检测到邀请码
        if ($input->invite) {
            $inviter = $this->model->where('invite_code', $input->invite)->find();
            if (!$inviter) throw new LogicException('邀请码不存在');
        }

        $data = [
            'username'    => $input->username,
            'password'    => $input->password,
            'mobile'      => $input->mobile ?? '',
            'email'       => $input->email ?? '',
            'invite_code' => Str::random(16), //随机生成一个邀请码
            'status'      => 1,
        ];
        $this->model->startTrans();
        try {
            /** @var User $user */
            $user = parent::save($data);
            if ($auth && $auth->login($user)) {
                $user->login_at = date('Y-m-d H:i:s');
                $user->save();
            }

            //注册赠送积分
            if ($score = (int)config('user.register_score', 0)) {
                app(UserLogic::class)
                    ->withUser($user)
                    ->addScore($score, 'register', '注册赠送');
            }

            //保存邀请记录
            if (!empty($inviter)) {
                try {
                    app(InviteLogic::class)->createLog($inviter, $user);
                } catch (LogicException) {
                }
            }

            //触发注册事件
            event('system.total', ['name' => 'user.reg']);
            event('user.register', ['user' => $user]);

            $this->model->commit();
            return $user;
        } catch (DbException $e) {
            $this->model->rollback();
            //保存到日志
            Log::error(var_export($e->getData(), true));
            throw new LogicException('注册失败，请稍后尝试');
        }
    }

    /**
     * @param $mobile
     * @param array $params
     * @return Model
     * @throws DbException
     */
    public function registerByMobile($mobile, array $params = []): Model
    {
        //生成用户名
        $username = Str::random(4, 2) . substr($mobile, -4);
        $params['username'] = $username;
        $params['mobile'] = $mobile;
        $params['password'] = Str::random(8);

        return $this->register((object)$params);
    }

    /**
     * 重置密码
     * @param string $type
     * @param string $account
     * @param string $password
     * @return true
     */
    public function resetPassword(string $type, string $account, string $password): bool
    {
        if (!in_array($type, ['email', 'mobile'])) {
            throw new LogicException('未知的找回方式');
        } elseif (!$account) {
            throw new LogicException('账号不能为空');
        }
        if (!$user = $this->model->where([$type => $account])->find()) {
            throw new LogicException('账号不存在');
        }
        $user->save(['password' => $password]); //模型会自动加密密码
        return true;
    }

    /**
     * 发送邮件验证码
     * @param string $email
     * @param string $event
     * @return void
     */
    public function sendCaptchaByEmail(string $email, string $event): void
    {
        $this->checkAccountExistsByCaptchaEvent('email', $email, $event);
        $logic = app(CaptchaLogic::class);
        try {
            //发送邮件验证码
            $logic->createCaptcha('email', $email, $event,
                fn($params) => $this->sendEmail($event, $email, $params)
            );
        } catch (\Exception $e) {
            Log::error(sprintf('邮件发送失败：%s（%s）', $e->getMessage(), $e->getCode()));
            throw new LogicException('邮件发送失败');
        }
    }

    /**
     * 发送短信验证码
     * @param string $mobile
     * @param string $event
     * @return void
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function sendCaptchaBySms(string $mobile, string $event): void
    {
        $this->checkAccountExistsByCaptchaEvent('mobile', $mobile, $event);
        $logic = app(CaptchaLogic::class);

        $cb = function (array $params) use ($event, $mobile) {
            return $this->sendSms($mobile, $event, $params);
        };
        //发送短信验证码
        $logic->createCaptcha('mobile', $mobile, $event, $cb);
    }

    /**
     * 检测验证码事件对应的账号判断
     * @param string $accountType
     * @param string $account
     * @param string $event
     * @return bool
     */
    protected function checkAccountExistsByCaptchaEvent(string $accountType, string $account, string $event): bool
    {
        $title = $accountType === 'email' ? '邮箱' : '手机号';
        if (in_array($event, ['register', 'bind'])) {
            if ($this->model->where($accountType, $account)->count() > 0) {
                throw new LogicException("{$title}已注册");
            }
        } else if (in_array($event, ['forget', 'unbind'])) {
            if (!$this->model->where($accountType, $account)->find()) {
                throw new LogicException("{$title}不存在");
            }
        }
        return true;
    }

    /**
     * 发送邮件
     * @param string $event
     * @param string $email
     * @param array $params
     * @return bool
     * @throws Exception
     */
    protected function sendEmail(string $event, string $email, array $params): bool
    {
        $config = [];
        foreach (config('system') as $key => $val) {
            if (str_starts_with($key, 'email_') || str_starts_with($key, 'smtp_')) {
                $config[$key] = $val;
            }
        }

        if (!$enum = CaptchaEmsEventEnum::tryFrom($event)) {
            throw new LogicException('未知验证码事件');
        }
        //标题
        $subject = $enum->label();
        //内容
        $body = $enum->getMailTemplate();

        $sendEmail = new Email($config);
        return $sendEmail->withSubject($subject)
            ->withBody($body, true)
            ->to($email, $params);
    }

    /**
     * 发送短信
     * @param string $mobile
     * @param string $event
     * @param array $params
     * @return bool
     */
    protected function sendSms(string $mobile, string $event, array $params): bool
    {
        if (!$provider = config('system.sms_driver')) {
            throw new LogicException('未配置短信服务');
        }
        $driver = Sms::getDriver($provider);
        return $driver->send($mobile, "user.{$event}", $params);
    }

}